package com.niceweather.android.logic.dao

import androidx.lifecycle.liveData
import com.niceweather.android.logic.model.Place
import com.niceweather.android.logic.model.Weather
import com.niceweather.android.logic.network.NiceWeatherNetwork
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import java.lang.RuntimeException
import kotlin.Exception
import kotlin.coroutines.CoroutineContext

/**
 *
 * Created by callhh on 2020/6/14
 * 仓库层 - 数据获取与缓存的中间层
 * 主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取，并将获得的数据返回给调用方
 */
object Repository {

    /**
     * 响应式编程的方式通知给上一层，通常会返回一个LiveData对象
     *  liveData(Dispatchers.IO)函数是lifecycle-livedata-ktx库提供的一个强大好用功能
     *  它可以自动构建并返回一个LiveData对象，然后在它的代码块中提供一个挂起函数的上下文，
     *  这样就可以在liveData()函数的代码块中调用任意的挂起函数
     *
     * 当前调用了NiceWeatherNetwork的searchPlaces()函数来搜索城市数据
     *
     */
//    fun searchPlaces(query:String) = liveData(Dispatchers.IO){
//        val result = try {
//            val placeResponse = NiceWeatherNetwork.searchPlaces(query)
//            if (placeResponse.status == "ok"){
//                val places = placeResponse.places
//
//                //1.成功，Kotlin内置的方法Result.success()来包装获取的城市数据列表
//                Result.success(places)
//            }else{
//
//                //2.失败，Result.failure()方法来包装一个异常信息
//                Result.failure(RuntimeException("response status is ${placeResponse.status}"))
//            }
//        }catch (e:Exception){
//            Result.failure<List<Place>>(e)
//        }
//        //3.emit()将包装的方法发射出去，类似于调用LiveData的setValue()方法来通知数据变化
//        //  只不过这里我们无法直接取得返回的LiveData对象,所以lifecycle-livedata-ktx库提供了这样一个替代方法
//        emit(result)
//    }

    fun searchPlaces(query:String) = fire(Dispatchers.IO){
            val placeResponse = NiceWeatherNetwork.searchPlaces(query)
            if (placeResponse.status == "ok"){
                val places = placeResponse.places

                //1.成功，Kotlin内置的方法Result.success()来包装获取的城市数据列表
                Result.success(places)
            }else{

                //2.失败，Result.failure()方法来包装一个异常信息
                Result.failure(RuntimeException("response status is ${placeResponse.status}"))
            }
    }

    /**
     * 刷新天气信息
     */
    fun refreshWeather(lng:String,lat:String) = fire (Dispatchers.IO){
            //携程作用域：为了调用async函数(作用：可以并发执行两个网络请求，保证两个网络请求成功相应之后 才能进行下一步程序)
            coroutineScope {
                val deferredRealtime = async {
                    NiceWeatherNetwork.getRealtimeWeather(lng, lat)
                }
                val deferredDaily = async {
                    NiceWeatherNetwork.getDailyWeather(lng, lat)
                }

                val realTimeResponse = deferredRealtime.await()
                val dailyResponse = deferredDaily.await()
                if (realTimeResponse.status == "ok" && dailyResponse.status == "ok"){
                    val weather = Weather(realTimeResponse.result.realtime, dailyResponse.result.daily)
                    Result.success(weather)
                }else{
                    Result.failure(RuntimeException("realtime response status is ${realTimeResponse.status}" +
                    " daily response status is ${dailyResponse.status}"))
                }
            }
    }

    /**
     * fire()函数
     * 按照liveData()函数得参数接收标准定义的一个高阶函数
     * 内部会先调用liveData()函数 然后在liveData()函数的代码块中统一进行了try catch处理。
     *  并在try语句中调用传入的Lambda表达式中的代码，最终获取Lambda表达式的执行结果并调用emit()方法发射出去
     *
     * 注意
     *  在liveData()函数的代码块中是有挂起函数上下文的，可是回调Lambda表达式中，代码就没有挂起函数上下文了。但实际上Lambda表达式中的代码一定也是在挂起函数中运行的
     *  为了解决此问题 我们需要在函数类型前声明一个 suspend 关键字，以表示所有传入的Lambda表达式中的代码也是拥有挂起函数上下文的。
     */
    private fun <T> fire(context:CoroutineContext,block:suspend () -> Result<T>) = liveData<Result<T>>(context) {
        val result = try {
            block()
        }catch (e:Exception){
            Result.failure<T>(e)
        }
        emit(result)
    }

    fun savePlace(place:Place) = PlaceDao.savePlace(place)

    fun getSavedPlace() = PlaceDao.getSavedPlace()

    fun isPlaceSaved() = PlaceDao.isPlaceSaved()




}